package com.tencent.cloud.iov.processor.action;

import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.tencent.cloud.iov.action.IActionHandler;
import com.tencent.cloud.iov.processor.utils.AppConst;
import com.tencent.cloud.iov.processor.utils.TypeElementComparator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

/**
 * 根分发类创建器
 *
 * 根据目标类型，分发至具体的分发类
 *
 * @author showxiao
 * @date 2018/4/18
 */

class ActionHandlerGenerator {

    private static final String CLASS_NAME_POSTFIX = "$ActionDispatcher";
    private static final String CLASS_NAME = "AnnotationActionHandler";

    private final ProcessingEnvironment mProcessingEnv;

    ActionHandlerGenerator(ProcessingEnvironment processingEnv) {
        mProcessingEnv = processingEnv;
    }

    /**
     public class RootActionDispatcher {

         public static <T, D> boolean dispatch(T target, String actionId, D actionData) {

             Class<?> clazz = target.getClass();
             if (RecyclerContract.View.class.isAssignableFrom(clazz)) {
                 return RecyclerContract$View$ActionDispatcher
                     .dispatch((RecyclerContract.View) target, actionId, actionData);
             }

             throw new IllegalStateException("No ActionDispatcher is defined for " + target + " with " +
             "actionId[" + actionId + "]");
         }
     }
     */
    void generate(Set<TypeElement> classElements)
            throws IOException {
        MethodSpec.Builder methodBuilder = createDispatchMethodBuilder();

        List<TypeElement> sortedList = getSortedTypeElements(classElements);

        // Class<?> clazz = target.getClass()
        methodBuilder.addStatement("Class<?> clazz = target.getClass()");

        addControlFlow(methodBuilder, sortedList);
        methodBuilder.endControlFlow();

        methodBuilder
                .addStatement("throw new IllegalStateException" +
                        "(\"No ActionDispatcher is defined for \" + target + \" with \" +\n" +
                        "\"actionId[\" + actionId + \"]\")");

        TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(IActionHandler.class)
                .addMethod(methodBuilder.build());

        typeBuilder = typeBuilder.addJavadoc(getJavaDoc());

        JavaFile.builder(AppConst.PACKAGE_NAME, typeBuilder.build())
                .build()
                .writeTo(mProcessingEnv.getFiler());
    }

    /**
     * 创建方法
     *
     * public static <T, D> boolean dispatch(T target, String actionId, D actionData)
     *
     * @return
     */
    private MethodSpec.Builder createDispatchMethodBuilder() {
        return MethodSpec.methodBuilder("handleAction")
                .addModifiers(Modifier.PUBLIC)
                .addTypeVariable(TypeVariableName.get("T"))
                .addTypeVariable(TypeVariableName.get("D"))
                .returns(boolean.class)
                .addParameter(TypeVariableName.get("T"), "target")
                .addParameter(String.class, "actionId")
                .addParameter(TypeVariableName.get("D"), "actionData")
                .addAnnotation(Override.class);
    }

    /**
     * 分发类排序
     *
     * 优先匹配子类，再匹配父类
     *
     * @param classElements
     * @return
     */
    private List<TypeElement> getSortedTypeElements(Set<TypeElement> classElements) {
        List<TypeElement> sortedList = new ArrayList<>(classElements);
        Collections.sort(sortedList, new TypeElementComparator());
        Collections.reverse(sortedList);
        return sortedList;
    }

    /**
     * 添加控制流
     *
     *  @param methodBuilder
     * @param sortedList
     */
    private void addControlFlow(MethodSpec.Builder methodBuilder, List<TypeElement> sortedList) {
        boolean hasBegun = false;
        for (TypeElement typeElement : sortedList) {

            String pkgName = mProcessingEnv.getElementUtils()
                    .getPackageOf(typeElement).getQualifiedName().toString();
            // RecyclerContract$View$ActionDispatcher
            String dispatcherClassName = pkgName + "."
                    + getDispatcherClassName(typeElement, mProcessingEnv);

            // if (RecyclerContract.View.class.isAssignableFrom(clazz))
            String ctrlPrefix = !hasBegun ? "if" : "else if";
            String ctrlStatement = "$L ($T.class.isAssignableFrom(clazz))";

            // return RecyclerContract$View$ActionDispatcher
            //   .dispatch((RecyclerContract.View) target, actionId, actionData)
            String exprStatement = "return $L\n.dispatch(($T) target, actionId, actionData)";

            // i.e. if () {} else if () {}
            if (!hasBegun) {
                methodBuilder.beginControlFlow(ctrlStatement, ctrlPrefix, typeElement.asType())
                        .addStatement(exprStatement, dispatcherClassName, typeElement.asType());
                hasBegun = true;
            } else {
                methodBuilder.nextControlFlow(ctrlStatement, ctrlPrefix, typeElement.asType())
                        .addStatement(exprStatement, dispatcherClassName, typeElement.asType());
            }
        }
    }

    private String getJavaDoc() {
        return "注解行为执行类：通过注解方式定义具体行为实现方法\n\n" +
                "此类通过编译时注解自动生成\n\n" +
                "对于需要定义目标行为的方法，只需要在方法上添加 @Action(YOUR_ACTION_ID)，\n" +
                "重新编译后，便会生成对应的行为分发类，此类将通过该行为分发类将目标行为分发至具体行为实现方法\n";
    }

    /**
     * 创建的 ActionDispatcher 类名称
     *
     * i.e. RecyclerContract$View$ActionDispatcher
     *
     * @param classElement
     * @return
     */
    static String getDispatcherClassName(TypeElement classElement,
                                         ProcessingEnvironment processingEnv) {
        String pkgName = processingEnv.getElementUtils()
                .getPackageOf(classElement).getQualifiedName().toString();
        String className = classElement.getQualifiedName().toString();
        String originClassName = className.substring(pkgName.length() + 1, className.length());
        return originClassName.replace('.', '$') + CLASS_NAME_POSTFIX;
    }

}
